package com.shujia.mllib

import org.apache.spark.mllib.linalg
import org.apache.spark.mllib.linalg.{SparseVector, Vectors}
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}

object Demo01Vector {
  def main(args: Array[String]): Unit = {
    /**
     * Spark MLLib中有两套API：ml、mllib
     * 一套是基于DataFrame的API：ml
     * 另一套是基于RDD的API：mllib
     */

    /**
     * 机器学习模型所能够识别的数据类型
     * 在Spark中通过向量Vectors表示
     * 可以分为两类：稀疏向量sparse、稠密向量dense
     */
    /**
     * 影响房价的因素：
     * 朝向：东0南1西2北3
     * 楼层：高2中1低0
     * 是否购买：买1 不买0
     * 面积 2  朝向3  楼层9 ......共计10个因素   标签：是否购买
     * 100   0    0                                 1
     * 88    1    2                                 0
     * 119   2    1                                 1
     */
    // 稠密向量的优点在于可以将所有特征值直接展示，简单方便
    val denseVector01: linalg.Vector = Vectors.dense(88, 1, 2)
    // 当有很多特征值为空或者0的时候，空间利用率比较低
    val denseVector02: linalg.Vector = Vectors.dense(88, 1, 0, 0, 0, 0, 0, 2)
    println(denseVector01)
    println(denseVector02)
    // 将稠密向量 转成稀疏向量
    // 当缺失值较多时用稀疏向量表示特征值 空间利用率上面会相对稠密向量更有优势
    val sparseVector: SparseVector = denseVector02.toSparse
    println(sparseVector)

    // 手动创建稀疏向量
    val sparseVector02: linalg.Vector = Vectors.sparse(10, Array(2, 3, 9), Array(119, 2, 1))
    println(sparseVector02)
    println(sparseVector02.toDense)

    // 1:5.3 2:3.5 3:2.5 4:106.4 5:67.5 6:69.1 7:83
    val vec01: linalg.Vector = Vectors.dense(5.3, 3.5, 2.5, 106.4, 67.5, 69.1, 83)

    val spark: SparkSession = SparkSession
      .builder()
      .master("local[*]")
      .appName("Demo01Vector")
      .getOrCreate()

    // 基于RDD加载SVM格式的数据
    // LabelPoint实际上就是带标签的向量
    // 机器学习所使用的历史数据一般可以分为两个部分：标签Label、特征Features
    // 特征Features可以使用向量表示 再加上Label标签即可形成LabelPoint
    val lPoint: LabeledPoint = LabeledPoint(1.0, sparseVector02)

    /**
     * 当用稀疏向量表示数据时，由于0的数据在稀疏向量中并不会保存，如果0的数据刚好在每条数据的最后的位置
     * 那么将其保存为libsvm格式时会造成特征数量的缺失
     * 在读取libsvm文件时可以手动指定数据的特征数
     */
    val svmRDD: RDD[LabeledPoint] = MLUtils.loadLibSVMFile(spark.sparkContext, "spark/data/mllib/data/人体指标.txt", 10)
    svmRDD.foreach(println)

    // 基于DataFrame的方式加载libsvm格式的数据
    val libsvmDF: DataFrame = spark
      .read
      .format("libsvm")
      .option("numFeatures", "10")
      .load("spark/data/mllib/data/人体指标.txt")

    libsvmDF.show()

    val lPointRDD: RDD[LabeledPoint] = spark.sparkContext.parallelize(List(lPoint))
    MLUtils.saveAsLibSVMFile(lPointRDD,"spark/data/mllib/data/mylibsvm")

  }

}
